source('../env.R')
Warning: cannot create dir '/Users/james/Projects/urban_community_structure_wrk', reason 'No such file or directory'Warning: cannot create dir '/Users/james/Projects/urban_community_structure_wrk/geo', reason 'No such file or directory'Warning: cannot create dir '/Users/james/Projects/urban_community_structure_wrk/ebird', reason 'No such file or directory'Warning: cannot create dir '/Users/james/Projects/urban_community_structure_wrk/birdlife', reason 'No such file or directory'Warning: cannot create dir '/Users/james/Projects/urban_community_structure_wrk/auth', reason 'No such file or directory'

Species in communities

It seems reasonable to expect that cities with simialr regional pools will have similar species entering the city, and thus a similar response to urbanisation.

Load data

communities = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'communities_for_analysis.csv'))
Rows: 2462 Columns: 9── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (5): city_name, jetz_species_name, seasonal, presence, origin
dbl (1): city_id
lgl (3): present_urban_high, present_urban_med, present_urban_low
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
communities_summary = communities %>% group_by(city_id) %>% summarise(
  regional_pool_size = n(), 
  urban_size_high = sum(present_urban_high), 
  urban_size_med = sum(present_urban_med), 
  urban_size_low = sum(present_urban_low)
)

communities_summary_long = bind_rows(
  communities_summary %>% rename(urban_pool_size = 'urban_size_high') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'High'),
  communities_summary %>% rename(urban_pool_size = 'urban_size_med') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'Medium'),
  communities_summary %>% rename(urban_pool_size = 'urban_size_low') %>% dplyr::select(city_id, regional_pool_size, urban_pool_size) %>% mutate(is_urban_threshold = 'Low')
)
communities_summary_long
city_points = st_centroid(read_sf(filename(CITY_DATA_OUTPUT_DIR, 'city_selection.shp')))
Warning: st_centroid assumes attributes are constant over geometriesWarning: st_centroid does not give correct centroids for longitude/latitude data
community_data_metrics = read_csv(filename(COMMUNITY_OUTPUT_DIR, 'community_assembly_metrics.csv')) %>%
  dplyr::select(city_id, mntd_normalised, fd_normalised, urban_pool_size, is_urban_threshold) %>%
  left_join(read_csv(filename(CITY_DATA_OUTPUT_DIR, 'realms.csv'))) %>%
  left_join(communities_summary_long) %>%
  left_join(city_points[,c('city_id', 'city_nm')]) %>%
  rename(city_name='city_nm') %>%
  mutate(is_urban_threshold = factor(is_urban_threshold, levels = c('low', 'medium', 'high'), labels = c('Low', 'Medium', 'High'), ordered = T)) %>%
  arrange(city_id)
Rows: 792 Columns: 93── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): is_urban_threshold
dbl (92): city_id, mntd_normalised, mntd_actual, mntd_min, mntd_max, mntd_mean, mntd_sd, fd_normalised, fd_actual, fd_min, fd_max, fd_m...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Rows: 342 Columns: 2── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): core_realm
dbl (1): city_id
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.Joining with `by = join_by(city_id)`Joining with `by = join_by(city_id, urban_pool_size, is_urban_threshold)`Joining with `by = join_by(city_id)`
community_data_metrics

Load trait data

traits = read_csv(filename(TAXONOMY_OUTPUT_DIR, 'traits_jetz.csv'))
Rows: 304 Columns: 5── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): jetz_species_name
dbl (4): gape_width, trophic_trait, locomotory_trait, mass
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(traits)
fetch_normalised_traits = function(required_species_list) {
  required_traits = traits %>% filter(jetz_species_name %in% required_species_list)
  
  required_traits$gape_width_normalised = normalise(required_traits$gape_width, min(required_traits$gape_width), max(required_traits$gape_width))
  required_traits$trophic_trait_normalised = normalise(required_traits$trophic_trait, min(required_traits$trophic_trait), max(required_traits$trophic_trait))
  required_traits$locomotory_trait_normalised = normalise(required_traits$locomotory_trait, min(required_traits$locomotory_trait), max(required_traits$locomotory_trait))
  required_traits$mass_normalised = normalise(required_traits$mass, min(required_traits$mass), max(required_traits$mass))
  
  traits_normalised_long = required_traits %>% pivot_longer(cols = c('gape_width_normalised', 'trophic_trait_normalised', 'locomotory_trait_normalised', 'mass_normalised'), names_to = 'trait', values_to = 'normalised_value') %>% dplyr::select(jetz_species_name, trait, normalised_value)
  traits_normalised_long$trait = factor(traits_normalised_long$trait, levels = c('gape_width_normalised', 'trophic_trait_normalised', 'locomotory_trait_normalised', 'mass_normalised'), labels = c('Gape Width', 'Trophic Trait', 'Locomotory Trait', 'Mass'))
  
  traits_normalised_long
}

fetch_normalised_traits(c('Aplopelia_larvata', 'Chalcophaps_indica', 'Caloenas_nicobarica'))

Read in our phylogeny

phylo_tree = read.tree(filename(TAXONOMY_OUTPUT_DIR, 'phylogeny.tre'))
ggtree(phylo_tree, layout='circular')

Load resolve ecoregions

resolve = read_resolve()
Warning: st_buffer does not correctly buffer longitude/latitude datadist is assumed to be in decimal degrees (arc_degrees).
Warning: st_simplify does not correctly simplify longitude/latitude data, dTolerance needs to be in decimal degrees

Create helper functions

to_species_matrix = function(filtered_communities) {
  filtered_communities %>% 
    dplyr::select(city_id, jetz_species_name) %>% 
    distinct() %>%
    mutate(present = TRUE) %>% 
    pivot_wider(
      names_from = jetz_species_name, 
      values_from = "present", 
      values_fill = list(present = F)
    ) %>% 
    tibble::column_to_rownames(var='city_id')
}
community_nmds = function(filtered_communities) {
  species_matrix = to_species_matrix(filtered_communities)
  nmds <- metaMDS(species_matrix, k=2, trymax = 30)
  nmds_result = data.frame(scores(nmds)$sites)
  nmds_result$city_id = as.double(rownames(scores(nmds)$sites))
  rownames(nmds_result) = NULL
  
  nmds_result
}

https://www.datacamp.com/tutorial/k-means-clustering-r

scree_plot = function(community_nmds_data) {
  # Decide how many clusters to look at
  n_clusters <- min(10, nrow(community_nmds_data) - 1)
  
  # Initialize total within sum of squares error: wss
  wss <- numeric(n_clusters)
  
  set.seed(123)
  
  # Look over 1 to n possible clusters
  for (i in 1:n_clusters) {
    # Fit the model: km.out
    km.out <- kmeans(community_nmds_data[,c('NMDS1','NMDS2')], centers = i, nstart = 20)
    # Save the within cluster sum of squares
    wss[i] <- km.out$tot.withinss
  }
  
  # Produce a scree plot
  wss_df <- tibble(clusters = 1:n_clusters, wss = wss)
   
  scree_plot <- ggplot(wss_df, aes(x = clusters, y = wss, group = 1)) +
      geom_point(size = 4)+
      geom_line() +
      scale_x_continuous(breaks = c(2, 4, 6, 8, 10)) +
      xlab('Number of clusters')
  scree_plot
}
cluster_cities = function(city_nmds, cities_community_data, centers) {
  set.seed(123)
  kmeans_clusters <- kmeans(city_nmds[,c('NMDS1', 'NMDS2')], centers = centers, nstart = 20)
  city_nmds$cluster = kmeans_clusters$cluster
  cities_community_data %>% left_join(city_nmds) %>% mutate(cluster = as.factor(cluster))
}
plot_nmds_clusters = function(cluster_cities) {
  cluster_cities %>% dplyr::select(city_id, city_name, NMDS1, NMDS2, cluster) %>% distinct() %>%
  ggplot(aes(x = NMDS1, y = NMDS2, colour = cluster)) + geom_point() + geom_label_repel(aes(label = city_name))
}
plot_city_cluster = function(city_cluster_data_metrics) {
  species_in_cluster = communities %>% 
    filter(city_id %in% city_cluster_data_metrics$city_id) %>% 
    group_by(jetz_species_name, city_name, present_urban_high, present_urban_med, present_urban_low) %>% 
    summarise() %>%
    mutate(present_score = present_urban_high + present_urban_med + present_urban_low)

  species_in_cluster$present_score = factor(species_in_cluster$present_score, levels = c(0, 1, 2, 3), labels = c('Regional Pool', 'Low Threshold', 'Medium Threshold', 'High Threshold'))
  
  tree_cropped <- ladderize(drop.tip(phylo_tree, setdiff(phylo_tree$tip.label, species_in_cluster$jetz_species_name)))
    
  gg_tree = ggtree(tree_cropped)
  
  gg_presence = ggplot(species_in_cluster, aes(x=city_name, y=jetz_species_name)) + 
          geom_tile(aes(fill=present_score)) + scale_fill_manual(values = c("green", "yellow", "orange", "red")) + 
          theme_minimal() + xlab(NULL) + ylab(NULL) + 
          theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
          labs(fill='Presence')
  
  species_in_cluster_traits = fetch_normalised_traits(species_in_cluster$jetz_species_name)
  
  gg_traits = ggplot(species_in_cluster_traits, aes(x = trait, y = jetz_species_name, size = normalised_value)) + geom_point() + theme_minimal() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1), axis.text.y=element_blank()) + xlab(NULL) + ylab(NULL) + labs(size = "Normalised Value")
  
  gg_cities_mntd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = mntd_normalised, fill = is_urban_threshold)) + geom_bar(stat = "identity", position = position_dodge(preserve = "single")) + scale_fill_manual(values = c("yellow", "orange", "red"))  + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("MNTD")
  
  gg_cities_fd = ggplot(city_cluster_data_metrics, aes(x = city_name, y = fd_normalised, fill = is_urban_threshold)) + geom_bar(stat = "identity", position = position_dodge(preserve = "single")) + scale_fill_manual(values = c("yellow", "orange", "red"))  + theme_minimal() + theme(legend.position = "none", axis.text.x=element_blank()) + xlab(NULL) + ylab("FD")
  
  gg_presence %>% insert_top(gg_cities_mntd, height = 0.5) %>% insert_top(gg_cities_fd, height = 0.5) %>% insert_left(gg_tree, width = 0.75) %>% insert_right(gg_traits, width = 0.5)
}

Nearctic

nearctic_cities = community_data_metrics %>% filter(core_realm == 'Nearctic')
nearctic_cities %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "San Jose"                 "Los Angeles"              "Concord"                  "Tijuana"                  "Bakersfield"             
 [6] "Fresno"                   "Sacramento"               "Mexicali"                 "Hermosillo"               "Las Vegas"               
[11] "Phoenix"                  "Tucson"                   "Durango"                  "Portland"                 "Chihuahua"               
[16] "Aguascalientes"           "Seattle"                  "Ciudad Juárez"            "San Luis Potosí"          "Mexico City"             
[21] "Saltillo"                 "Vancouver"                "Albuquerque"              "Monterrey"                "Nuevo Laredo"            
[26] "San Antonio"              "Austin"                   "Houston"                  "Dallas"                   "Oklahoma City"           
[31] "Calgary"                  "New Orleans"              "Kansas City"              "Omaha"                    "St. Louis"               
[36] "Bradenton"                "Tampa"                    "Minneapolis [Saint Paul]" "Atlanta"                  "Orlando"                 
[41] "Louisville"               "Chicago"                  "Indianapolis"             "Milwaukee"                "Virginia Beach"          
[46] "New York"                
nearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% nearctic_cities$city_id)) 
Run 0 stress 0.1161938 
Run 1 stress 0.1602926 
Run 2 stress 0.1161938 
... Procrustes: rmse 0.0000391137  max resid 0.0001220354 
... Similar to previous best
Run 3 stress 0.1180524 
Run 4 stress 0.114161 
... New best solution
... Procrustes: rmse 0.02952835  max resid 0.1653388 
Run 5 stress 0.1633841 
Run 6 stress 0.1161938 
Run 7 stress 0.1148408 
Run 8 stress 0.1145839 
... Procrustes: rmse 0.005101997  max resid 0.02603849 
Run 9 stress 0.1148409 
Run 10 stress 0.1194245 
Run 11 stress 0.114161 
... Procrustes: rmse 0.0000131053  max resid 0.00003247269 
... Similar to previous best
Run 12 stress 0.1601457 
Run 13 stress 0.1152814 
Run 14 stress 0.1145839 
... Procrustes: rmse 0.005101925  max resid 0.02603476 
Run 15 stress 0.1593986 
Run 16 stress 0.1152814 
Run 17 stress 0.1152814 
Run 18 stress 0.1148408 
Run 19 stress 0.1166567 
Run 20 stress 0.114161 
... New best solution
... Procrustes: rmse 0.00001100111  max resid 0.00003043996 
... Similar to previous best
*** Best solution repeated 1 times
nearctic_cities_nmds
scree_plot(nearctic_cities_nmds)

nearctic_cities = cluster_cities(city_nmds = nearctic_cities_nmds, cities_community_data = nearctic_cities, centers = 3)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(nearctic_cities)

nearctic_biomes = st_crop(resolve[resolve$REALM == 'Nearctic',c('REALM')], xmin = -220, ymin = 0, xmax = 0, ymax = 70)
although coordinates are longitude/latitude, st_intersection assumes that they are planar
Warning: attribute variables are assumed to be spatially constant throughout all geometries
 
ggplot() + 
  geom_sf(data = nearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = nearctic_cities, aes(geometry = geometry, color = cluster))

Neartic Cluster 1

nearctic_cities %>% filter(cluster == 1) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Neartic Cluster 2

nearctic_cities %>% filter(cluster == 2) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Neartic Cluster 3

nearctic_cities %>% filter(cluster == 3) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Neotropic

neotropic_cities = community_data_metrics %>% filter(core_realm == 'Neotropic')
neotropic_cities %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Culiacán"                  "Guadalajara"               "Morelia"                   "Acapulco"                 
 [5] "Querétaro"                 "Cuernavaca"                "Puebla"                    "Oaxaca"                   
 [9] "Xalapa"                    "Veracruz"                  "Tuxtla Gutiérrez"          "Villahermosa"             
[13] "Guatemala City"            "San Salvador"              "San Pedro Sula"            "Mérida"                   
[17] "Tegucigalpa"               "Managua"                   "San José"                  "Cancún"                   
[21] "Guayaquil"                 "Chiclayo"                  "Panama City"               "Trujillo"                 
[25] "Quito"                     "Havana"                    "Cali"                      "Lima"                     
[29] "Pereira"                   "Miami"                     "Medellín"                  "Ibagué"                   
[33] "Cartagena"                 "Kingston"                  "Bogota"                    "Barranquilla"             
[37] "Bucaramanga"               "Cúcuta"                    "Maracaibo"                 "Arequipa"                 
[41] "Barquisimeto"              "Santo Domingo"             "Maracay"                   "El Alto [La Paz]"         
[45] "Caracas"                   "Cochabamba"                "Viña del Mar [Valparaíso]" "Río Piedras [San Juan]"   
[49] "Barcelona"                 "Concepción"                "Santiago"                  "Mendoza"                  
[53] "Salta"                     "Cordoba"                   "Asuncion"                  "Buenos Aires"             
[57] "La Plata"                  "Ciudad del Este"           "Montevideo"                "Mar del Plata"            
[61] "Porto Alegre"              "São Paulo"                 "Santos"                    "Sao Jose dos Campos"      
neotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% neotropic_cities$city_id)) 
Run 0 stress 0.134619 
Run 1 stress 0.1348237 
... Procrustes: rmse 0.01071648  max resid 0.05474937 
Run 2 stress 0.1348046 
... Procrustes: rmse 0.01065841  max resid 0.05478104 
Run 3 stress 0.134433 
... New best solution
... Procrustes: rmse 0.006824728  max resid 0.0457213 
Run 4 stress 0.1405635 
Run 5 stress 0.1412221 
Run 6 stress 0.1366602 
Run 7 stress 0.1348047 
... Procrustes: rmse 0.006555296  max resid 0.04610324 
Run 8 stress 0.1346375 
... Procrustes: rmse 0.007008375  max resid 0.04563686 
Run 9 stress 0.1366488 
Run 10 stress 0.1344509 
... Procrustes: rmse 0.001187276  max resid 0.006962109 
... Similar to previous best
Run 11 stress 0.140254 
Run 12 stress 0.1348046 
... Procrustes: rmse 0.006551589  max resid 0.04607907 
Run 13 stress 0.1402524 
Run 14 stress 0.1346406 
... Procrustes: rmse 0.007269969  max resid 0.04574296 
Run 15 stress 0.134619 
... Procrustes: rmse 0.006830556  max resid 0.04571227 
Run 16 stress 0.1348046 
... Procrustes: rmse 0.006545341  max resid 0.04604589 
Run 17 stress 0.1406945 
Run 18 stress 0.1344509 
... Procrustes: rmse 0.001186797  max resid 0.006956028 
... Similar to previous best
Run 19 stress 0.1346368 
... Procrustes: rmse 0.006934877  max resid 0.04571238 
Run 20 stress 0.141222 
*** Best solution repeated 2 times
neotropic_cities_nmds
scree_plot(neotropic_cities_nmds)

neotropic_cities = cluster_cities(city_nmds = neotropic_cities_nmds, cities_community_data = neotropic_cities, centers = 3)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(neotropic_cities)

neotropic_biomes = resolve[resolve$REALM == 'Neotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = neotropic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = neotropic_cities, aes(geometry = geometry, color = cluster))

Neotropic Cluster 1

neotropic_cities %>% filter(cluster == 1) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Neotropic Cluster 2

neotropic_cities %>% filter(cluster == 2) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Neotropic Cluster 3

neotropic_cities %>% filter(cluster == 3) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Palearctic

palearctic_cities = community_data_metrics %>% filter(core_realm == 'Palearctic')
palearctic_cities %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
 [1] "Lisbon"                "Porto"                 "Marrakesh"             "Seville"               "Dublin"               
 [6] "Málaga"                "Madrid"                "Glasgow"               "Bilbao"                "Liverpool"            
[11] "Bristol"               "Manchester"            "Birmingham"            "Leeds"                 "Newcastle upon Tyne"  
[16] "Sheffield"             "Nottingham"            "Valencia"              "London"                "Toulouse"             
[21] "Paris"                 "Barcelona"             "Rotterdam [The Hague]" "Brussels"              "Amsterdam"            
[26] "Lyon"                  "Marseille"             "Dusseldorf"            "Nice"                  "Frankfurt am Main"    
[31] "Zurich"                "Oslo"                  "Stuttgart"             "Hamburg"               "Genoa"                
[36] "Nuremberg"             "Copenhagen"            "Munich"                "Berlin"                "Dresden"              
[41] "Rome"                  "Prague"                "Stockholm"             "Poznan"                "Vienna"               
[46] "Wroclaw"               "Zagreb"                "Gdansk"                "Budapest"              "Krakow"               
[51] "Warsaw"                "Helsinki"              "Riga"                  "Belgrade"              "Lviv"                 
[56] "Sofia"                 "Thessaloniki"          "Minsk"                 "Athens"                "Kyiv"                 
[61] "Istanbul"              "Odesa"                 "Samsun"                "Luxor"                 "Tel Aviv"             
[66] "Jerusalem"             "Tbilisi"               "Yerevan"               "Abu Dhabi"             "Dubai"                
[71] "Bishkek"              
palearctic_cities_nmds = community_nmds(communities %>% filter(city_id %in% palearctic_cities$city_id)) 
Run 0 stress 0.04203818 
Run 1 stress 0.05819353 
Run 2 stress 0.04984179 
Run 3 stress 0.06377882 
Run 4 stress 0.04580606 
Run 5 stress 0.04337554 
Run 6 stress 0.1267478 
Run 7 stress 0.05064886 
Run 8 stress 0.1058666 
Run 9 stress 0.08693039 
Run 10 stress 0.04500686 
Run 11 stress 0.05497039 
Run 12 stress 0.04351811 
Run 13 stress 0.1568467 
Run 14 stress 0.1373195 
Run 15 stress 0.0431116 
Run 16 stress 0.05064975 
Run 17 stress 0.07403216 
Run 18 stress 0.04354788 
Run 19 stress 0.04203775 
... New best solution
... Procrustes: rmse 0.000102071  max resid 0.0003739103 
... Similar to previous best
Run 20 stress 0.06461453 
*** Best solution repeated 1 times
palearctic_cities_nmds
scree_plot(palearctic_cities_nmds)

palearctic_cities = cluster_cities(city_nmds = palearctic_cities_nmds, cities_community_data = palearctic_cities, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(palearctic_cities)

palearctic_biomes = resolve[resolve$REALM == 'Palearctic',c('REALM')]
 
ggplot() + 
  geom_sf(data = palearctic_biomes, aes(geometry = geometry)) + 
  geom_sf(data = palearctic_cities, aes(geometry = geometry, color = cluster))

Palearctic Cluster 1

palearctic_cities %>% filter(cluster == 1) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Palearctic Cluster 2

palearctic_cities %>% filter(cluster == 2) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Palearctic Cluster 3

palearctic_cities %>% filter(cluster == 3) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Palearctic Cluster 4

palearctic_cities %>% filter(cluster == 4) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Afrotropic

afrotropic_cities = community_data_metrics %>% filter(core_realm == 'Afrotropic')
afrotropic_cities %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
[1] "Cape Town"    "Johannesburg" "Pretoria"     "Kigali"       "Kampala"      "Arusha"       "Nairobi"      "Addis Ababa" 
afrotropic_cities_nmds = community_nmds(communities %>% filter(city_id %in% afrotropic_cities$city_id)) 
Run 0 stress 0.06095916 
Run 1 stress 0.06095916 
... Procrustes: rmse 0.00001563648  max resid 0.00002846132 
... Similar to previous best
Run 2 stress 0.06095904 
... New best solution
... Procrustes: rmse 0.0002654117  max resid 0.0004793866 
... Similar to previous best
Run 3 stress 0.1246142 
Run 4 stress 0.06095907 
... Procrustes: rmse 0.0002020247  max resid 0.0003683555 
... Similar to previous best
Run 5 stress 0.06095908 
... Procrustes: rmse 0.0002145394  max resid 0.0003892005 
... Similar to previous best
Run 6 stress 0.06095922 
... Procrustes: rmse 0.0003263648  max resid 0.0005977107 
... Similar to previous best
Run 7 stress 0.06095938 
... Procrustes: rmse 0.0005326933  max resid 0.0009871214 
... Similar to previous best
Run 8 stress 0.1246143 
Run 9 stress 0.198614 
Run 10 stress 0.06095908 
... Procrustes: rmse 0.0001415359  max resid 0.0002526003 
... Similar to previous best
Run 11 stress 0.0609591 
... Procrustes: rmse 0.0001761283  max resid 0.000316345 
... Similar to previous best
Run 12 stress 0.06095931 
... Procrustes: rmse 0.0003961754  max resid 0.0007336573 
... Similar to previous best
Run 13 stress 0.06095922 
... Procrustes: rmse 0.0003270253  max resid 0.0005854539 
... Similar to previous best
Run 14 stress 0.06095933 
... Procrustes: rmse 0.0005079837  max resid 0.0009130458 
... Similar to previous best
Run 15 stress 0.126785 
Run 16 stress 0.1352924 
Run 17 stress 0.06095934 
... Procrustes: rmse 0.0003673878  max resid 0.0006914034 
... Similar to previous best
Run 18 stress 0.1246142 
Run 19 stress 0.1254289 
Run 20 stress 0.06095926 
... Procrustes: rmse 0.0003421154  max resid 0.0006007065 
... Similar to previous best
*** Best solution repeated 12 times
afrotropic_cities_nmds
scree_plot(afrotropic_cities_nmds)

afrotropic_cities = cluster_cities(city_nmds = afrotropic_cities_nmds, cities_community_data = afrotropic_cities, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(afrotropic_cities)

afrotropicbiomes = resolve[resolve$REALM == 'Afrotropic',c('REALM')]
 
ggplot() + 
  geom_sf(data = afrotropicbiomes, aes(geometry = geometry)) + 
  geom_sf(data = afrotropic_cities, aes(geometry = geometry, color = cluster))

Afrotropic Cluster 1

afrotropic_cities %>% filter(cluster == 1) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Afrotropic Cluster 2

afrotropic_cities %>% filter(cluster == 2) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Afrotropic Cluster 3

afrotropic_cities %>% filter(cluster == 3) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Afrotropic Cluster 4

afrotropic_cities %>% filter(cluster == 4) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Indomalayan

indomalayan_cities = community_data_metrics %>% filter(core_realm == 'Indomalayan')
indomalayan_cities %>% dplyr::select(city_name) %>% distinct() %>% as.list()
$city_name
  [1] "Swat City"           "Srinagar"            "Jamnagar"            "Jammu"               "Rajkot"              "Bikaner"            
  [7] "Jodhpur"             "Jalandhar"           "Ahmedabad"           "Bhavnagar"           "Ludhiana"            "Anand"              
 [13] "Udaipur"             "Surat"               "Vadodara"            "Ajmer"               "Chandigarh"          "Vasai-Virar"        
 [19] "Mumbai"              "Jaipur"              "Delhi [New Delhi]"   "Nashik"              "Dehradun"            "Kota"               
 [25] "Pune"                "Haridwar"            "Dhule"               "Ujjain"              "Indore"              "Ahmadnagar"         
 [31] "Kolhapur"            "Jalgaon"             "Agra"                "Aurangabad"          "Sangli"              "Belagavi"           
 [37] "Gwalior"             "Budaun"              "Bareilly"            "Dharwad"             "Bhopal"              "Bhind"              
 [43] "Mangaluru"           "Solapur"             "Vijayapura"          "Akola"               "Latur"               "Kannur"             
 [49] "Davanagere"          "Thalassery"          "Amravati"            "Kalaburagi"          "Kozhikode"           "Guruvayur"          
 [55] "Malappuram"          "Lucknow"             "Thrissur"            "Mysuru"              "Kochi"               "Nagpur"             
 [61] "Kollam"              "Jabalpur"            "Ettumanoor"          "Hyderabad"           "Coimbatore"          "Bengaluru"          
 [67] "Thiruvananthapuram"  "Tiruppur"            "Faizabad"            "Erode"               "Prayagraj"           "Pratapgarh"         
 [73] "Salem"               "Dindigul"            "Madurai"             "Tiruchirappalli"     "Durg"                "Vellore"            
 [79] "Tirupati"            "Raipur"              "Bilaspur"            "Vijayawada"          "Puducherry"          "Chennai"            
 [85] "Kathmandu"           "Colombo"             "Rajamahendravaram"   "Patna"               "Kandy"               "Bihar Sharif"       
 [91] "Visakhapatnam"       "Ranchi"              "Brahmapur"           "Jamshedpur"          "Darjeeling"          "Siliguri"           
 [97] "Cuttack"             "Bhubaneshwar"        "Jalpaiguri"          "Berhampore"          "Kolkata"             "Krishnanagar"       
[103] "Guwahati [Dispur]"   "Agartala"            "Silchar"             "Dimapur"             "Bangkok"             "George Town"        
[109] "Kuala Lumpur"        "Phnom Penh"          "Singapore"           "Hong Kong"           "Sha Tin"             "Hsinchu"            
[115] "Taichung"            "New Taipei [Taipei]" "Tainan"              "Denpasar"            "Kaohsiung"           "Kota Kinabalu"      
indomalayan_cities_nmds = community_nmds(communities %>% filter(city_id %in% indomalayan_cities$city_id)) 
Run 0 stress 0.1199339 
Run 1 stress 0.1149925 
... New best solution
... Procrustes: rmse 0.02333645  max resid 0.2337271 
Run 2 stress 0.1462678 
Run 3 stress 0.1396866 
Run 4 stress 0.1259837 
Run 5 stress 0.1423493 
Run 6 stress 0.1219108 
Run 7 stress 0.1592393 
Run 8 stress 0.156701 
Run 9 stress 0.1469659 
Run 10 stress 0.1179961 
Run 11 stress 0.1247678 
Run 12 stress 0.147121 
Run 13 stress 0.1175222 
Run 14 stress 0.1537648 
Run 15 stress 0.129813 
Run 16 stress 0.1583941 
Run 17 stress 0.1413598 
Run 18 stress 0.156746 
Run 19 stress 0.1550026 
Run 20 stress 0.1301376 
Run 21 stress 0.1171922 
Run 22 stress 0.1674604 
Run 23 stress 0.1286946 
Run 24 stress 0.1654557 
Run 25 stress 0.1318567 
Run 26 stress 0.1160446 
Run 27 stress 0.120831 
Run 28 stress 0.1241018 
Run 29 stress 0.1167138 
Run 30 stress 0.1589124 
*** Best solution was not repeated -- monoMDS stopping criteria:
     1: no. of iterations >= maxit
    29: stress ratio > sratmax
indomalayan_cities_nmds
scree_plot(indomalayan_cities_nmds)

indomalayan_cities = cluster_cities(city_nmds = indomalayan_cities_nmds, cities_community_data = indomalayan_cities, centers = 4)
Joining with `by = join_by(city_id)`
plot_nmds_clusters(indomalayan_cities)

indomalayan_biomes = resolve[resolve$REALM == 'Indomalayan',c('REALM')]
 
ggplot() + 
  geom_sf(data = indomalayan_biomes, aes(geometry = geometry)) + 
  geom_sf(data = indomalayan_cities, aes(geometry = geometry, color = cluster))

Indomalayan Cluster 1

indomalayan_cities %>% filter(cluster == 1) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Indomalayan Cluster 2

indomalayan_cities %>% filter(cluster == 2) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Indomalayan Cluster 3

indomalayan_cities %>% filter(cluster == 3) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

Indomalayan Cluster 4

indomalayan_cities %>% filter(cluster == 4) %>% plot_city_cluster()
`summarise()` has grouped output by 'jetz_species_name', 'city_name', 'present_urban_high', 'present_urban_med'. You can override using the `.groups` argument.

LS0tCnRpdGxlOiAiRGVlcCBkaXZlIHJlZ2lvbmFsIGNvbW11bml0aWVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYmlibGlvZ3JhcGh5OiAuLi9yZWYuYmliICAKLS0tCgpgYGB7cn0Kc291cmNlKCcuLi9lbnYuUicpCmBgYAoKIyBTcGVjaWVzIGluIGNvbW11bml0aWVzCkl0IHNlZW1zIHJlYXNvbmFibGUgdG8gZXhwZWN0IHRoYXQgY2l0aWVzIHdpdGggc2ltaWFsciByZWdpb25hbCBwb29scyB3aWxsIGhhdmUgc2ltaWxhciBzcGVjaWVzIGVudGVyaW5nIHRoZSBjaXR5LCBhbmQgdGh1cyBhIHNpbWlsYXIgcmVzcG9uc2UgdG8gdXJiYW5pc2F0aW9uLgoKIyMgTG9hZCBkYXRhCmBgYHtyfQpjb21tdW5pdGllcyA9IHJlYWRfY3N2KGZpbGVuYW1lKENPTU1VTklUWV9PVVRQVVRfRElSLCAnY29tbXVuaXRpZXNfZm9yX2FuYWx5c2lzLmNzdicpKQoKY29tbXVuaXRpZXNfc3VtbWFyeSA9IGNvbW11bml0aWVzICU+JSBncm91cF9ieShjaXR5X2lkKSAlPiUgc3VtbWFyaXNlKAogIHJlZ2lvbmFsX3Bvb2xfc2l6ZSA9IG4oKSwgCiAgdXJiYW5fc2l6ZV9oaWdoID0gc3VtKHByZXNlbnRfdXJiYW5faGlnaCksIAogIHVyYmFuX3NpemVfbWVkID0gc3VtKHByZXNlbnRfdXJiYW5fbWVkKSwgCiAgdXJiYW5fc2l6ZV9sb3cgPSBzdW0ocHJlc2VudF91cmJhbl9sb3cpCikKCmNvbW11bml0aWVzX3N1bW1hcnlfbG9uZyA9IGJpbmRfcm93cygKICBjb21tdW5pdGllc19zdW1tYXJ5ICU+JSByZW5hbWUodXJiYW5fcG9vbF9zaXplID0gJ3VyYmFuX3NpemVfaGlnaCcpICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIHJlZ2lvbmFsX3Bvb2xfc2l6ZSwgdXJiYW5fcG9vbF9zaXplKSAlPiUgbXV0YXRlKGlzX3VyYmFuX3RocmVzaG9sZCA9ICdIaWdoJyksCiAgY29tbXVuaXRpZXNfc3VtbWFyeSAlPiUgcmVuYW1lKHVyYmFuX3Bvb2xfc2l6ZSA9ICd1cmJhbl9zaXplX21lZCcpICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfaWQsIHJlZ2lvbmFsX3Bvb2xfc2l6ZSwgdXJiYW5fcG9vbF9zaXplKSAlPiUgbXV0YXRlKGlzX3VyYmFuX3RocmVzaG9sZCA9ICdNZWRpdW0nKSwKICBjb21tdW5pdGllc19zdW1tYXJ5ICU+JSByZW5hbWUodXJiYW5fcG9vbF9zaXplID0gJ3VyYmFuX3NpemVfbG93JykgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgcmVnaW9uYWxfcG9vbF9zaXplLCB1cmJhbl9wb29sX3NpemUpICU+JSBtdXRhdGUoaXNfdXJiYW5fdGhyZXNob2xkID0gJ0xvdycpCikKY29tbXVuaXRpZXNfc3VtbWFyeV9sb25nCmBgYApgYGB7cn0KY2l0eV9wb2ludHMgPSBzdF9jZW50cm9pZChyZWFkX3NmKGZpbGVuYW1lKENJVFlfREFUQV9PVVRQVVRfRElSLCAnY2l0eV9zZWxlY3Rpb24uc2hwJykpKQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfZGF0YV9tZXRyaWNzID0gcmVhZF9jc3YoZmlsZW5hbWUoQ09NTVVOSVRZX09VVFBVVF9ESVIsICdjb21tdW5pdHlfYXNzZW1ibHlfbWV0cmljcy5jc3YnKSkgJT4lCiAgZHBseXI6OnNlbGVjdChjaXR5X2lkLCBtbnRkX25vcm1hbGlzZWQsIGZkX25vcm1hbGlzZWQsIHVyYmFuX3Bvb2xfc2l6ZSwgaXNfdXJiYW5fdGhyZXNob2xkKSAlPiUKICBsZWZ0X2pvaW4ocmVhZF9jc3YoZmlsZW5hbWUoQ0lUWV9EQVRBX09VVFBVVF9ESVIsICdyZWFsbXMuY3N2JykpKSAlPiUKICBsZWZ0X2pvaW4oY29tbXVuaXRpZXNfc3VtbWFyeV9sb25nKSAlPiUKICBsZWZ0X2pvaW4oY2l0eV9wb2ludHNbLGMoJ2NpdHlfaWQnLCAnY2l0eV9ubScpXSkgJT4lCiAgcmVuYW1lKGNpdHlfbmFtZT0nY2l0eV9ubScpICU+JQogIG11dGF0ZShpc191cmJhbl90aHJlc2hvbGQgPSBmYWN0b3IoaXNfdXJiYW5fdGhyZXNob2xkLCBsZXZlbHMgPSBjKCdsb3cnLCAnbWVkaXVtJywgJ2hpZ2gnKSwgbGFiZWxzID0gYygnTG93JywgJ01lZGl1bScsICdIaWdoJyksIG9yZGVyZWQgPSBUKSkgJT4lCiAgYXJyYW5nZShjaXR5X2lkKQoKY29tbXVuaXR5X2RhdGFfbWV0cmljcwpgYGAKCkxvYWQgdHJhaXQgZGF0YQpgYGB7cn0KdHJhaXRzID0gcmVhZF9jc3YoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3RyYWl0c19qZXR6LmNzdicpKQpoZWFkKHRyYWl0cykKYGBgCmBgYHtyfQpmZXRjaF9ub3JtYWxpc2VkX3RyYWl0cyA9IGZ1bmN0aW9uKHJlcXVpcmVkX3NwZWNpZXNfbGlzdCkgewogIHJlcXVpcmVkX3RyYWl0cyA9IHRyYWl0cyAlPiUgZmlsdGVyKGpldHpfc3BlY2llc19uYW1lICVpbiUgcmVxdWlyZWRfc3BlY2llc19saXN0KQogIAogIHJlcXVpcmVkX3RyYWl0cyRnYXBlX3dpZHRoX25vcm1hbGlzZWQgPSBub3JtYWxpc2UocmVxdWlyZWRfdHJhaXRzJGdhcGVfd2lkdGgsIG1pbihyZXF1aXJlZF90cmFpdHMkZ2FwZV93aWR0aCksIG1heChyZXF1aXJlZF90cmFpdHMkZ2FwZV93aWR0aCkpCiAgcmVxdWlyZWRfdHJhaXRzJHRyb3BoaWNfdHJhaXRfbm9ybWFsaXNlZCA9IG5vcm1hbGlzZShyZXF1aXJlZF90cmFpdHMkdHJvcGhpY190cmFpdCwgbWluKHJlcXVpcmVkX3RyYWl0cyR0cm9waGljX3RyYWl0KSwgbWF4KHJlcXVpcmVkX3RyYWl0cyR0cm9waGljX3RyYWl0KSkKICByZXF1aXJlZF90cmFpdHMkbG9jb21vdG9yeV90cmFpdF9ub3JtYWxpc2VkID0gbm9ybWFsaXNlKHJlcXVpcmVkX3RyYWl0cyRsb2NvbW90b3J5X3RyYWl0LCBtaW4ocmVxdWlyZWRfdHJhaXRzJGxvY29tb3RvcnlfdHJhaXQpLCBtYXgocmVxdWlyZWRfdHJhaXRzJGxvY29tb3RvcnlfdHJhaXQpKQogIHJlcXVpcmVkX3RyYWl0cyRtYXNzX25vcm1hbGlzZWQgPSBub3JtYWxpc2UocmVxdWlyZWRfdHJhaXRzJG1hc3MsIG1pbihyZXF1aXJlZF90cmFpdHMkbWFzcyksIG1heChyZXF1aXJlZF90cmFpdHMkbWFzcykpCiAgCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZyA9IHJlcXVpcmVkX3RyYWl0cyAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKCdnYXBlX3dpZHRoX25vcm1hbGlzZWQnLCAndHJvcGhpY190cmFpdF9ub3JtYWxpc2VkJywgJ2xvY29tb3RvcnlfdHJhaXRfbm9ybWFsaXNlZCcsICdtYXNzX25vcm1hbGlzZWQnKSwgbmFtZXNfdG8gPSAndHJhaXQnLCB2YWx1ZXNfdG8gPSAnbm9ybWFsaXNlZF92YWx1ZScpICU+JSBkcGx5cjo6c2VsZWN0KGpldHpfc3BlY2llc19uYW1lLCB0cmFpdCwgbm9ybWFsaXNlZF92YWx1ZSkKICB0cmFpdHNfbm9ybWFsaXNlZF9sb25nJHRyYWl0ID0gZmFjdG9yKHRyYWl0c19ub3JtYWxpc2VkX2xvbmckdHJhaXQsIGxldmVscyA9IGMoJ2dhcGVfd2lkdGhfbm9ybWFsaXNlZCcsICd0cm9waGljX3RyYWl0X25vcm1hbGlzZWQnLCAnbG9jb21vdG9yeV90cmFpdF9ub3JtYWxpc2VkJywgJ21hc3Nfbm9ybWFsaXNlZCcpLCBsYWJlbHMgPSBjKCdHYXBlIFdpZHRoJywgJ1Ryb3BoaWMgVHJhaXQnLCAnTG9jb21vdG9yeSBUcmFpdCcsICdNYXNzJykpCiAgCiAgdHJhaXRzX25vcm1hbGlzZWRfbG9uZwp9CgpmZXRjaF9ub3JtYWxpc2VkX3RyYWl0cyhjKCdBcGxvcGVsaWFfbGFydmF0YScsICdDaGFsY29waGFwc19pbmRpY2EnLCAnQ2Fsb2VuYXNfbmljb2JhcmljYScpKQpgYGAKCgpSZWFkIGluIG91ciBwaHlsb2dlbnkKYGBge3J9CnBoeWxvX3RyZWUgPSByZWFkLnRyZWUoZmlsZW5hbWUoVEFYT05PTVlfT1VUUFVUX0RJUiwgJ3BoeWxvZ2VueS50cmUnKSkKZ2d0cmVlKHBoeWxvX3RyZWUsIGxheW91dD0nY2lyY3VsYXInKQpgYGAKCkxvYWQgcmVzb2x2ZSBlY29yZWdpb25zCmBgYHtyfQpyZXNvbHZlID0gcmVhZF9yZXNvbHZlKCkKYGBgCgojIyBDcmVhdGUgaGVscGVyIGZ1bmN0aW9ucwpgYGB7cn0KdG9fc3BlY2llc19tYXRyaXggPSBmdW5jdGlvbihmaWx0ZXJlZF9jb21tdW5pdGllcykgewogIGZpbHRlcmVkX2NvbW11bml0aWVzICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgamV0el9zcGVjaWVzX25hbWUpICU+JSAKICAgIGRpc3RpbmN0KCkgJT4lCiAgICBtdXRhdGUocHJlc2VudCA9IFRSVUUpICU+JSAKICAgIHBpdm90X3dpZGVyKAogICAgICBuYW1lc19mcm9tID0gamV0el9zcGVjaWVzX25hbWUsIAogICAgICB2YWx1ZXNfZnJvbSA9ICJwcmVzZW50IiwgCiAgICAgIHZhbHVlc19maWxsID0gbGlzdChwcmVzZW50ID0gRikKICAgICkgJT4lIAogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXModmFyPSdjaXR5X2lkJykKfQpgYGAKCmBgYHtyfQpjb21tdW5pdHlfbm1kcyA9IGZ1bmN0aW9uKGZpbHRlcmVkX2NvbW11bml0aWVzKSB7CiAgc3BlY2llc19tYXRyaXggPSB0b19zcGVjaWVzX21hdHJpeChmaWx0ZXJlZF9jb21tdW5pdGllcykKICBubWRzIDwtIG1ldGFNRFMoc3BlY2llc19tYXRyaXgsIGs9MiwgdHJ5bWF4ID0gMzApCiAgbm1kc19yZXN1bHQgPSBkYXRhLmZyYW1lKHNjb3JlcyhubWRzKSRzaXRlcykKICBubWRzX3Jlc3VsdCRjaXR5X2lkID0gYXMuZG91YmxlKHJvd25hbWVzKHNjb3JlcyhubWRzKSRzaXRlcykpCiAgcm93bmFtZXMobm1kc19yZXN1bHQpID0gTlVMTAogIAogIG5tZHNfcmVzdWx0Cn0KYGBgCgpodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvay1tZWFucy1jbHVzdGVyaW5nLXIKYGBge3J9CnNjcmVlX3Bsb3QgPSBmdW5jdGlvbihjb21tdW5pdHlfbm1kc19kYXRhKSB7CiAgIyBEZWNpZGUgaG93IG1hbnkgY2x1c3RlcnMgdG8gbG9vayBhdAogIG5fY2x1c3RlcnMgPC0gbWluKDEwLCBucm93KGNvbW11bml0eV9ubWRzX2RhdGEpIC0gMSkKICAKICAjIEluaXRpYWxpemUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIGVycm9yOiB3c3MKICB3c3MgPC0gbnVtZXJpYyhuX2NsdXN0ZXJzKQogIAogIHNldC5zZWVkKDEyMykKICAKICAjIExvb2sgb3ZlciAxIHRvIG4gcG9zc2libGUgY2x1c3RlcnMKICBmb3IgKGkgaW4gMTpuX2NsdXN0ZXJzKSB7CiAgICAjIEZpdCB0aGUgbW9kZWw6IGttLm91dAogICAga20ub3V0IDwtIGttZWFucyhjb21tdW5pdHlfbm1kc19kYXRhWyxjKCdOTURTMScsJ05NRFMyJyldLCBjZW50ZXJzID0gaSwgbnN0YXJ0ID0gMjApCiAgICAjIFNhdmUgdGhlIHdpdGhpbiBjbHVzdGVyIHN1bSBvZiBzcXVhcmVzCiAgICB3c3NbaV0gPC0ga20ub3V0JHRvdC53aXRoaW5zcwogIH0KICAKICAjIFByb2R1Y2UgYSBzY3JlZSBwbG90CiAgd3NzX2RmIDwtIHRpYmJsZShjbHVzdGVycyA9IDE6bl9jbHVzdGVycywgd3NzID0gd3NzKQogICAKICBzY3JlZV9wbG90IDwtIGdncGxvdCh3c3NfZGYsIGFlcyh4ID0gY2x1c3RlcnMsIHkgPSB3c3MsIGdyb3VwID0gMSkpICsKICAgICAgZ2VvbV9wb2ludChzaXplID0gNCkrCiAgICAgIGdlb21fbGluZSgpICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMiwgNCwgNiwgOCwgMTApKSArCiAgICAgIHhsYWIoJ051bWJlciBvZiBjbHVzdGVycycpCiAgc2NyZWVfcGxvdAp9CmBgYAoKYGBge3J9CmNsdXN0ZXJfY2l0aWVzID0gZnVuY3Rpb24oY2l0eV9ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEsIGNlbnRlcnMpIHsKICBzZXQuc2VlZCgxMjMpCiAga21lYW5zX2NsdXN0ZXJzIDwtIGttZWFucyhjaXR5X25tZHNbLGMoJ05NRFMxJywgJ05NRFMyJyldLCBjZW50ZXJzID0gY2VudGVycywgbnN0YXJ0ID0gMjApCiAgY2l0eV9ubWRzJGNsdXN0ZXIgPSBrbWVhbnNfY2x1c3RlcnMkY2x1c3RlcgogIGNpdGllc19jb21tdW5pdHlfZGF0YSAlPiUgbGVmdF9qb2luKGNpdHlfbm1kcykgJT4lIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQp9CmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyA9IGZ1bmN0aW9uKGNsdXN0ZXJfY2l0aWVzKSB7CiAgY2x1c3Rlcl9jaXRpZXMgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9pZCwgY2l0eV9uYW1lLCBOTURTMSwgTk1EUzIsIGNsdXN0ZXIpICU+JSBkaXN0aW5jdCgpICU+JQogIGdncGxvdChhZXMoeCA9IE5NRFMxLCB5ID0gTk1EUzIsIGNvbG91ciA9IGNsdXN0ZXIpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gY2l0eV9uYW1lKSkKfQpgYGAKCmBgYHtyfQpwbG90X2NpdHlfY2x1c3RlciA9IGZ1bmN0aW9uKGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MpIHsKICBzcGVjaWVzX2luX2NsdXN0ZXIgPSBjb21tdW5pdGllcyAlPiUgCiAgICBmaWx0ZXIoY2l0eV9pZCAlaW4lIGNpdHlfY2x1c3Rlcl9kYXRhX21ldHJpY3MkY2l0eV9pZCkgJT4lIAogICAgZ3JvdXBfYnkoamV0el9zcGVjaWVzX25hbWUsIGNpdHlfbmFtZSwgcHJlc2VudF91cmJhbl9oaWdoLCBwcmVzZW50X3VyYmFuX21lZCwgcHJlc2VudF91cmJhbl9sb3cpICU+JSAKICAgIHN1bW1hcmlzZSgpICU+JQogICAgbXV0YXRlKHByZXNlbnRfc2NvcmUgPSBwcmVzZW50X3VyYmFuX2hpZ2ggKyBwcmVzZW50X3VyYmFuX21lZCArIHByZXNlbnRfdXJiYW5fbG93KQoKICBzcGVjaWVzX2luX2NsdXN0ZXIkcHJlc2VudF9zY29yZSA9IGZhY3RvcihzcGVjaWVzX2luX2NsdXN0ZXIkcHJlc2VudF9zY29yZSwgbGV2ZWxzID0gYygwLCAxLCAyLCAzKSwgbGFiZWxzID0gYygnUmVnaW9uYWwgUG9vbCcsICdMb3cgVGhyZXNob2xkJywgJ01lZGl1bSBUaHJlc2hvbGQnLCAnSGlnaCBUaHJlc2hvbGQnKSkKICAKICB0cmVlX2Nyb3BwZWQgPC0gbGFkZGVyaXplKGRyb3AudGlwKHBoeWxvX3RyZWUsIHNldGRpZmYocGh5bG9fdHJlZSR0aXAubGFiZWwsIHNwZWNpZXNfaW5fY2x1c3RlciRqZXR6X3NwZWNpZXNfbmFtZSkpKQogICAgCiAgZ2dfdHJlZSA9IGdndHJlZSh0cmVlX2Nyb3BwZWQpCiAgCiAgZ2dfcHJlc2VuY2UgPSBnZ3Bsb3Qoc3BlY2llc19pbl9jbHVzdGVyLCBhZXMoeD1jaXR5X25hbWUsIHk9amV0el9zcGVjaWVzX25hbWUpKSArIAogICAgICAgICAgZ2VvbV90aWxlKGFlcyhmaWxsPXByZXNlbnRfc2NvcmUpKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImdyZWVuIiwgInllbGxvdyIsICJvcmFuZ2UiLCAicmVkIikpICsgCiAgICAgICAgICB0aGVtZV9taW5pbWFsKCkgKyB4bGFiKE5VTEwpICsgeWxhYihOVUxMKSArIAogICAgICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKSArIAogICAgICAgICAgbGFicyhmaWxsPSdQcmVzZW5jZScpCiAgCiAgc3BlY2llc19pbl9jbHVzdGVyX3RyYWl0cyA9IGZldGNoX25vcm1hbGlzZWRfdHJhaXRzKHNwZWNpZXNfaW5fY2x1c3RlciRqZXR6X3NwZWNpZXNfbmFtZSkKICAKICBnZ190cmFpdHMgPSBnZ3Bsb3Qoc3BlY2llc19pbl9jbHVzdGVyX3RyYWl0cywgYWVzKHggPSB0cmFpdCwgeSA9IGpldHpfc3BlY2llc19uYW1lLCBzaXplID0gbm9ybWFsaXNlZF92YWx1ZSkpICsgZ2VvbV9wb2ludCgpICsgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpLCBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCkpICsgeGxhYihOVUxMKSArIHlsYWIoTlVMTCkgKyBsYWJzKHNpemUgPSAiTm9ybWFsaXNlZCBWYWx1ZSIpCiAgCiAgZ2dfY2l0aWVzX21udGQgPSBnZ3Bsb3QoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgYWVzKHggPSBjaXR5X25hbWUsIHkgPSBtbnRkX25vcm1hbGlzZWQsIGZpbGwgPSBpc191cmJhbl90aHJlc2hvbGQpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHByZXNlcnZlID0gInNpbmdsZSIpKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoInllbGxvdyIsICJvcmFuZ2UiLCAicmVkIikpICArIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKSArIHhsYWIoTlVMTCkgKyB5bGFiKCJNTlREIikKICAKICBnZ19jaXRpZXNfZmQgPSBnZ3Bsb3QoY2l0eV9jbHVzdGVyX2RhdGFfbWV0cmljcywgYWVzKHggPSBjaXR5X25hbWUsIHkgPSBmZF9ub3JtYWxpc2VkLCBmaWxsID0gaXNfdXJiYW5fdGhyZXNob2xkKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZShwcmVzZXJ2ZSA9ICJzaW5nbGUiKSkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJ5ZWxsb3ciLCAib3JhbmdlIiwgInJlZCIpKSAgKyB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKyB4bGFiKE5VTEwpICsgeWxhYigiRkQiKQogIAogIGdnX3ByZXNlbmNlICU+JSBpbnNlcnRfdG9wKGdnX2NpdGllc19tbnRkLCBoZWlnaHQgPSAwLjUpICU+JSBpbnNlcnRfdG9wKGdnX2NpdGllc19mZCwgaGVpZ2h0ID0gMC41KSAlPiUgaW5zZXJ0X2xlZnQoZ2dfdHJlZSwgd2lkdGggPSAwLjc1KSAlPiUgaW5zZXJ0X3JpZ2h0KGdnX3RyYWl0cywgd2lkdGggPSAwLjUpCn0KYGBgCgojIyBOZWFyY3RpYwpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ05lYXJjdGljJykKbmVhcmN0aWNfY2l0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIG5lYXJjdGljX2NpdGllcyRjaXR5X2lkKSkgCm5lYXJjdGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QobmVhcmN0aWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9Cm5lYXJjdGljX2NpdGllcyA9IGNsdXN0ZXJfY2l0aWVzKGNpdHlfbm1kcyA9IG5lYXJjdGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBuZWFyY3RpY19jaXRpZXMsIGNlbnRlcnMgPSAzKQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMobmVhcmN0aWNfY2l0aWVzKQpgYGAKCmBgYHtyfQpuZWFyY3RpY19iaW9tZXMgPSBzdF9jcm9wKHJlc29sdmVbcmVzb2x2ZSRSRUFMTSA9PSAnTmVhcmN0aWMnLGMoJ1JFQUxNJyldLCB4bWluID0gLTIyMCwgeW1pbiA9IDAsIHhtYXggPSAwLCB5bWF4ID0gNzApCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZWFyY3RpY19jaXRpZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBjb2xvciA9IGNsdXN0ZXIpKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMQpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMgpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBOZWFydGljIENsdXN0ZXIgMwpgYGB7cn0KbmVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIE5lb3Ryb3BpYwpgYGB7cn0KbmVvdHJvcGljX2NpdGllcyA9IGNvbW11bml0eV9kYXRhX21ldHJpY3MgJT4lIGZpbHRlcihjb3JlX3JlYWxtID09ICdOZW90cm9waWMnKQpuZW90cm9waWNfY2l0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzX25tZHMgPSBjb21tdW5pdHlfbm1kcyhjb21tdW5pdGllcyAlPiUgZmlsdGVyKGNpdHlfaWQgJWluJSBuZW90cm9waWNfY2l0aWVzJGNpdHlfaWQpKSAKbmVvdHJvcGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QobmVvdHJvcGljX2NpdGllc19ubWRzKQpgYGAKCmBgYHtyfQpuZW90cm9waWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gbmVvdHJvcGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBuZW90cm9waWNfY2l0aWVzLCBjZW50ZXJzID0gMykKYGBgCgpgYGB7cn0KcGxvdF9ubWRzX2NsdXN0ZXJzKG5lb3Ryb3BpY19jaXRpZXMpCmBgYAoKYGBge3J9Cm5lb3Ryb3BpY19iaW9tZXMgPSByZXNvbHZlW3Jlc29sdmUkUkVBTE0gPT0gJ05lb3Ryb3BpYycsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IG5lb3Ryb3BpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBuZW90cm9waWNfY2l0aWVzLCBhZXMoZ2VvbWV0cnkgPSBnZW9tZXRyeSwgY29sb3IgPSBjbHVzdGVyKSkKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgMQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQpuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKIyMjIE5lb3Ryb3BpYyBDbHVzdGVyIDIKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KbmVvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMikgJT4lIHBsb3RfY2l0eV9jbHVzdGVyKCkKYGBgCgojIyMgTmVvdHJvcGljIENsdXN0ZXIgMwpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQpuZW90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIFBhbGVhcmN0aWMKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzID0gY29tbXVuaXR5X2RhdGFfbWV0cmljcyAlPiUgZmlsdGVyKGNvcmVfcmVhbG0gPT0gJ1BhbGVhcmN0aWMnKQpwYWxlYXJjdGljX2NpdGllcyAlPiUgZHBseXI6OnNlbGVjdChjaXR5X25hbWUpICU+JSBkaXN0aW5jdCgpICU+JSBhcy5saXN0KCkKYGBgCgpgYGB7cn0KcGFsZWFyY3RpY19jaXRpZXNfbm1kcyA9IGNvbW11bml0eV9ubWRzKGNvbW11bml0aWVzICU+JSBmaWx0ZXIoY2l0eV9pZCAlaW4lIHBhbGVhcmN0aWNfY2l0aWVzJGNpdHlfaWQpKSAKcGFsZWFyY3RpY19jaXRpZXNfbm1kcwpgYGAKCmBgYHtyfQpzY3JlZV9wbG90KHBhbGVhcmN0aWNfY2l0aWVzX25tZHMpCmBgYAoKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gcGFsZWFyY3RpY19jaXRpZXNfbm1kcywgY2l0aWVzX2NvbW11bml0eV9kYXRhID0gcGFsZWFyY3RpY19jaXRpZXMsIGNlbnRlcnMgPSA0KQpgYGAKCmBgYHtyfQpwbG90X25tZHNfY2x1c3RlcnMocGFsZWFyY3RpY19jaXRpZXMpCmBgYAoKYGBge3J9CnBhbGVhcmN0aWNfYmlvbWVzID0gcmVzb2x2ZVtyZXNvbHZlJFJFQUxNID09ICdQYWxlYXJjdGljJyxjKCdSRUFMTScpXQogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gcGFsZWFyY3RpY19iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBwYWxlYXJjdGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmBgYAoKIyMjIFBhbGVhcmN0aWMgQ2x1c3RlciAxCmBgYHtyfQpwYWxlYXJjdGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkgJT4lIHBsb3RfY2l0eV9jbHVzdGVyKCkKYGBgCgojIyMgUGFsZWFyY3RpYyBDbHVzdGVyIDIKYGBge3J9CnBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgMwpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTZ9CnBhbGVhcmN0aWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAzKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBQYWxlYXJjdGljIENsdXN0ZXIgNApgYGB7cn0KcGFsZWFyY3RpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpICU+JSBwbG90X2NpdHlfY2x1c3RlcigpCmBgYAoKIyMgQWZyb3Ryb3BpYwpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXMgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnQWZyb3Ryb3BpYycpCmFmcm90cm9waWNfY2l0aWVzICU+JSBkcGx5cjo6c2VsZWN0KGNpdHlfbmFtZSkgJT4lIGRpc3RpbmN0KCkgJT4lIGFzLmxpc3QoKQpgYGAKCmBgYHtyfQphZnJvdHJvcGljX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgYWZyb3Ryb3BpY19jaXRpZXMkY2l0eV9pZCkpIAphZnJvdHJvcGljX2NpdGllc19ubWRzCmBgYAoKYGBge3J9CnNjcmVlX3Bsb3QoYWZyb3Ryb3BpY19jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXMgPSBjbHVzdGVyX2NpdGllcyhjaXR5X25tZHMgPSBhZnJvdHJvcGljX2NpdGllc19ubWRzLCBjaXRpZXNfY29tbXVuaXR5X2RhdGEgPSBhZnJvdHJvcGljX2NpdGllcywgY2VudGVycyA9IDQpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhhZnJvdHJvcGljX2NpdGllcykKYGBgCgpgYGB7cn0KYWZyb3Ryb3BpY2Jpb21lcyA9IHJlc29sdmVbcmVzb2x2ZSRSRUFMTSA9PSAnQWZyb3Ryb3BpYycsYygnUkVBTE0nKV0KIApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGFmcm90cm9waWNiaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBhZnJvdHJvcGljX2NpdGllcywgYWVzKGdlb21ldHJ5ID0gZ2VvbWV0cnksIGNvbG9yID0gY2x1c3RlcikpCmBgYAoKIyMjIEFmcm90cm9waWMgQ2x1c3RlciAxCmBgYHtyfQphZnJvdHJvcGljX2NpdGllcyAlPiUgZmlsdGVyKGNsdXN0ZXIgPT0gMSkgJT4lIHBsb3RfY2l0eV9jbHVzdGVyKCkKYGBgCgojIyMgQWZyb3Ryb3BpYyBDbHVzdGVyIDIKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBBZnJvdHJvcGljIENsdXN0ZXIgMwpgYGB7cn0KYWZyb3Ryb3BpY19jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpICU+JSBwbG90X2NpdHlfY2x1c3RlcigpCmBgYAojIyMgQWZyb3Ryb3BpYyBDbHVzdGVyIDQKYGBge3J9CmFmcm90cm9waWNfY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSA0KSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIEluZG9tYWxheWFuCmBgYHtyfQppbmRvbWFsYXlhbl9jaXRpZXMgPSBjb21tdW5pdHlfZGF0YV9tZXRyaWNzICU+JSBmaWx0ZXIoY29yZV9yZWFsbSA9PSAnSW5kb21hbGF5YW4nKQppbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGRwbHlyOjpzZWxlY3QoY2l0eV9uYW1lKSAlPiUgZGlzdGluY3QoKSAlPiUgYXMubGlzdCgpCmBgYAoKYGBge3J9CmluZG9tYWxheWFuX2NpdGllc19ubWRzID0gY29tbXVuaXR5X25tZHMoY29tbXVuaXRpZXMgJT4lIGZpbHRlcihjaXR5X2lkICVpbiUgaW5kb21hbGF5YW5fY2l0aWVzJGNpdHlfaWQpKSAKaW5kb21hbGF5YW5fY2l0aWVzX25tZHMKYGBgCgpgYGB7cn0Kc2NyZWVfcGxvdChpbmRvbWFsYXlhbl9jaXRpZXNfbm1kcykKYGBgCgpgYGB7cn0KaW5kb21hbGF5YW5fY2l0aWVzID0gY2x1c3Rlcl9jaXRpZXMoY2l0eV9ubWRzID0gaW5kb21hbGF5YW5fY2l0aWVzX25tZHMsIGNpdGllc19jb21tdW5pdHlfZGF0YSA9IGluZG9tYWxheWFuX2NpdGllcywgY2VudGVycyA9IDQpCmBgYAoKYGBge3J9CnBsb3Rfbm1kc19jbHVzdGVycyhpbmRvbWFsYXlhbl9jaXRpZXMpCmBgYAoKYGBge3J9CmluZG9tYWxheWFuX2Jpb21lcyA9IHJlc29sdmVbcmVzb2x2ZSRSRUFMTSA9PSAnSW5kb21hbGF5YW4nLGMoJ1JFQUxNJyldCiAKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBpbmRvbWFsYXlhbl9iaW9tZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5KSkgKyAKICBnZW9tX3NmKGRhdGEgPSBpbmRvbWFsYXlhbl9jaXRpZXMsIGFlcyhnZW9tZXRyeSA9IGdlb21ldHJ5LCBjb2xvciA9IGNsdXN0ZXIpKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDEKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KaW5kb21hbGF5YW5fY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAxKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDIKYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMn0KaW5kb21hbGF5YW5fY2l0aWVzICU+JSBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUgcGxvdF9jaXR5X2NsdXN0ZXIoKQpgYGAKCiMjIyBJbmRvbWFsYXlhbiBDbHVzdGVyIDMKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD04fQppbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDMpICU+JSBwbG90X2NpdHlfY2x1c3RlcigpCmBgYAojIyMgSW5kb21hbGF5YW4gQ2x1c3RlciA0CmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQppbmRvbWFsYXlhbl9jaXRpZXMgJT4lIGZpbHRlcihjbHVzdGVyID09IDQpICU+JSBwbG90X2NpdHlfY2x1c3RlcigpCmBgYA==